

declare interface JSONObject {
    [key: string]: any
}

export interface ItemDataConfInfo {
    /**
     * 每一行的id字段的名称，在首列传递。描述每一行的id从何处取值。
     *
     * 例：为"uid"，每一行的id取data[i].uid的值。
     */
    idField?: string;
    /**
     * 层级
     */
    marginGrade?: string,
    /**
     * 父级id
     */
    parentIdField?: string;
    /**
     * 是否可见的域的名称。
     */
    visibleField?: string;
	/**
	 * data中表示该项是否不可用的字段的名称。
	 */
    disabledField?: string,
    /**
     * 这一列单元格的值字段的名称。描述这一列的单元格值从何处取值。
     *
     * 默认从该列id代表的字段中取值。
     *
     * 例：为"path"，这一列单元格的值取data[i].path的值。
     */
    valueField?: string;
	/**
	 * 这一列单元格的标题字段的名称。描述这一列的单元格的标题文本从何处取值。
	 *
     * 默认从该列id代表的字段中取值。
     *
     * 例：为"fileTypeName"，这一列单元格的标题文本取data[i].fileTypeName的值。
	 */
    captionField?: string;
    /**
     * html的域的名称。
     */
    htmlField?: string;
    /**
     * 描述的域的名称。
     */
    descField?: string;
    /**
     * 这一列单元格的图标字段的名称。描述这一列的单元格图标从何处取值。
     *
     * 例：为"image"，这一列单元格的图标取data[i].image的值。
     */
    iconField?: string;
    /**
     * 这列单元格是否显示图标，默认false，无图标。为true时，iconField才有效。
     */
    iconVisible?: boolean;
	/**
	 * 字体图标类型的域的名称
	 */
    iconFamilyField?: string;
    /**
     * 勾选的域的名称。
     */
    checkedField?: string;
    /**
     * 是否选中的域的名称。
     */
    selectedField?: string;
    /**
     * 子节点数组的域的名称。
     */
    childrenField?: string;
    /**
     * 是否没有子节点的域的名称。
     */
    isLeafField?: string;
    /**
     * 是否有子节点的域的名称。
     */
    hasChildrenField?: string;
	/**
	 * 每一行的css类名字段的名称，在首列传递。描述每一行的css类名从何处取值。
	 *
	 * 例：为"css"，每一行的css类名取data[i].css的值。
	 */
    classNameField?: string;
	/**
	 * data的一个属性，获取分组id，如果item需要分组显示，需要这个属性来标识属于哪个分组。
	 */
    groupIDField?: string;
	/**
	 * data的一个属性，用于获取title。
	 */
    titleField?: string;
	/**
	 * data的一个属性，获取image信息。
	 */
    imageField?: string;
	/**
	 * 装饰图标的域的名称。装饰图标是位于节点图标右上角的一个图标，一般是一个小圆点。
	 */
    decorateIconField?: string;
	/**
	 * 有装饰图标时，鼠标移入到节点图标的hint的域的名称。
	 */
    decorateIconDescField?: string;

	/**
	 * 每一行的计数字段
	 * 使用场景：https://jira.succez.com/browse/BI-33303 树、列表、下拉框等控件支持在节点上显示计数
	 */
    dataCountField?: string;
}

export const DEF_ITEM_DATA_CONF: ItemDataConfInfo = {
    idField: "id",
    marginGrade: "marginGrade",
    parentIdField: "parentId",
    visibleField: "visible",
    disabledField: "disabled",
    valueField: "value",
    captionField: "caption",
    htmlField: "html",
    descField: "desc",
    iconField: "icon",
    iconFamilyField: "fontFamily",
    checkedField: "checked",
    selectedField: "selected",
    childrenField: "childs",
    isLeafField: "leaf",
    hasChildrenField: "parent",
    classNameField: "className",
    groupIDField: "groupId",
    titleField: "title",
    imageField: "image",
    decorateIconField: "decorateIcon",
    decorateIconDescField: "decorateIconDesc",
    dataCountField: "dataCount"
};

export interface DataProviderArgs<T> {
	/**
	 * 此类所持有的数据，会被提供给数据的使用者。也可以是字符串数组，会自动根据国际化补全参数。
	 */
    data?: T[],

	/**
	 * 返回数据的函数，在loadData中调用。
	 */
    dataFunc?: () => Promise<T[]>,

	/**
	 * 是否使用子节点。为true时，DP提供的数据、搜索等的源头是递归遍历的所有节点；为false只是第一级节点。
	 * 默认true。
	 */
    childrenItemsEnabled?: boolean;
	/**
	 * 用于说明如何从data中读取value、caption等信息。
	 */
    itemDataConf?: ItemDataConfInfo,
	/**
	 * ImageView 的分组信息。
	 */
    groups?: Array<{ id: string, caption?: string }>;
	/**
	 * 国际化前缀，用于自动完成国际化。
	 */
    keyPrefix?: string
}


export class DataProvider<T extends JSONObject>  {
    public itemDataConf: ItemDataConfInfo;
    public groups: Array<{ id: string, caption?: string }>;
    public data: T[];
    public dataFunc: () => Promise<T[]>;
    protected childrenItemsEnabled: boolean;
    private keyPrefix: string;

	/**
	 * 是否需要重新获取数据。为true时，会再次调用loadData加载数据。
	 */
    protected needRefresh: boolean;

    protected loadDataPromise: Promise<any>;

    constructor(args: DataProviderArgs<T> = {}) {
        this.childrenItemsEnabled = args.childrenItemsEnabled !== undefined ? args.childrenItemsEnabled : true;
        this.keyPrefix = args.keyPrefix || null;
        this.groups = args.groups || null;
        this.itemDataConf = Object.assign({}, DEF_ITEM_DATA_CONF, args.itemDataConf);
        this.dataFunc = args.dataFunc;
        this.data = args.data;
    }

    public setData(data: T[]) {
        this.data = data;
    }

    protected ensureLoadedData() {
        // 既保证多次重复请求返回同一个promise，又保证强制刷新时返回的是最新数据。
        if (!this.needRefresh) {
            if (this.loadDataPromise) {
                return this.loadDataPromise;
            }
            if (this.data) {
                return Promise.resolve(this.data);
            }
            return this.requestLoadData().then(() => {
                return this.data;
            });
        }
        this.needRefresh = false;
        return this.requestLoadData().then(() => {
            return this.data;
        });
    }

    /**
     *在loadData完成之后，返回data，并对多次请求进行区分，只保留最后一次的数据请求
     */
    protected requestLoadData(): Promise<any> {
        let promise: Promise<any> = null;
        return promise = this.loadDataPromise = this.loadData().then((data) => {
            this.data = data || [];
            //如果在第一个loadData解析完成之后，又发生了一个promise，则不会理会之前所有的未完成的loadData
            if (promise != this.loadDataPromise) {
                return;
            }
            this.loadDataPromise = null;
            return data;
        }).catch((reason) => {
            this.loadDataPromise = null;
            return Promise.reject(reason);
        });
    }

    protected loadData(): Promise<T[]> {
        return this.dataFunc ? this.dataFunc() : Promise.resolve(this.data);
    }

    /**
     * 只是在ensureLoadedData完成resolve之后才会实际执行，因此返回的也是一个Promise，
     * 需要根据数据源和实际传入参数重写，从而为客户端提供数据
     * @param args 
     */
    public fetchData(args?: { parentData?: T, startIndex?: number, fetchCount?: number }): Promise<Array<T>> {
        return this.ensureLoadedData().then(() => {
            let result: T[];
            let data = this.data;
            if (!args || !args.parentData) {
                //初始化
                result = data.filter((item) => {
                    return item[this.itemDataConf.marginGrade] === 0 ? true : false;
                })
            } else {
                result = data.filter((item) => {
                    return item[this.itemDataConf.parentIdField] === args.parentData[this.itemDataConf.idField] ? true : false;
                })
            };
            return Promise.resolve(result);
        })
    }
}
